Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Displaying Raw • Download


Examples/Filetransfer.py HEAD (3535327f) Text, 20.16 KB

T8b949e##########################################################
T8b949e# This RNS example demonstrates a simple filetransfer #
T8b949e# server and client program. The server will serve a #
T8b949e# directory of files, and the clients can list and #
T8b949e# download files from the server. #
T8b949e# #
T8b949e# Please note that using RNS Resources for large file #
T8b949e# transfers is not recommended, since compression, #
T8b949e# encryption and hashmap sequencing can take a long time #
T8b949e# on systems with slow CPUs, which will probably result #
T8b949e# in the client timing out before the resource sender #
T8b949e# can complete preparing the resource. #
T8b949e# #
T8b949e# If you need to transfer large files, use the Bundle #
T8b949e# class instead, which will automatically slice the data #
T8b949e# into chunks suitable for packing as a Resource. #
T8b949e##########################################################

Tff7b72import T7ee787os
Tff7b72import T7ee787sys
Tff7b72import T7ee787time
Tff7b72import T7ee787threading
Tff7b72import T7ee787argparse
Tff7b72import T7ee787RNS
Tff7b72import T7ee787RNST7ee787.T7ee787vendorT7ee787.T7ee787umsgpack Tff7b72as T7ee787umsgpack

T8b949e# Let's define an app name. We'll use this for all
T8b949e# destinations we create. Since this echo example
T8b949e# is part of a range of example utilities, we'll put
T8b949e# them all within the app namespace "example_utilities"
Te6edf3APP_NAME Tff7b72= Ta5d6ff"Ta5d6ffexample_utilitiesTa5d6ff"

T8b949e# We'll also define a default timeout, in seconds
Te6edf3APP_TIMEOUT Tff7b72= T79c0ff45.0

T8b949e##########################################################
T8b949e#### Server Part #########################################
T8b949e##########################################################

Te6edf3serve_path Tff7b72= Tff7b72None

T8b949e# This initialisation is executed when the users chooses
T8b949e# to run as a server
Tff7b72def Td2a8ffserverTb4b4b4(Te6edf3configpathTb4b4b4, Te6edf3pathTb4b4b4)Tb4b4b4:
T8b949e# We must first initialise Reticulum
Te6edf3reticulum Tff7b72= Te6edf3RNSTff7b72.Td2a8ffReticulumTb4b4b4(Te6edf3configpathTb4b4b4)
T8b949e# Randomly create a new identity for our file server
Te6edf3server_identity Tff7b72= Te6edf3RNSTff7b72.Td2a8ffIdentityTb4b4b4(Tb4b4b4)

Tff7b72global Te6edf3serve_path
Te6edf3serve_path Tff7b72= Te6edf3path

T8b949e# We create a destination that clients can connect to. We
T8b949e# want clients to create links to this destination, so we
T8b949e# need to create a "single" destination type.
Te6edf3server_destination Tff7b72= Te6edf3RNSTff7b72.Td2a8ffDestinationTb4b4b4(
Te6edf3server_identityTb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffDestinationTff7b72.Td2a8ffINTb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffDestinationTff7b72.Td2a8ffSINGLETb4b4b4,
Te6edf3APP_NAMETb4b4b4,
Ta5d6ff"Ta5d6fffiletransferTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffserverTa5d6ff"
Tb4b4b4)

T8b949e# We configure a function that will get called every time
T8b949e# a new client creates a link to this destination.
Te6edf3server_destinationTff7b72.Td2a8ffset_link_established_callbackTb4b4b4(Te6edf3client_connectedTb4b4b4)

T8b949e# Everything's ready!
T8b949e# Let's Wait for client requests or user input
Te6edf3announceLoopTb4b4b4(Te6edf3server_destinationTb4b4b4)

Tff7b72def Td2a8ffannounceLoopTb4b4b4(Te6edf3destinationTb4b4b4)Tb4b4b4:
T8b949e# Let the user know that everything is ready
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffFile server Ta5d6ff"Tff7b72+Te6edf3RNSTff7b72.Td2a8ffprettyhexrepTb4b4b4(Te6edf3destinationTff7b72.Td2a8ffhashTb4b4b4)Tff7b72+Ta5d6ff"Ta5d6ff runningTa5d6ff"Tb4b4b4)
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffHit enter to manually send an announce (Ctrl-C to quit)Ta5d6ff"Tb4b4b4)

T8b949e# We enter a loop that runs until the users exits.
T8b949e# If the user hits enter, we will announce our server
T8b949e# destination on the network, which will let clients
T8b949e# know how to create messages directed towards it.
Tff7b72while Tff7b72TrueTb4b4b4:
Te6edf3entered Tff7b72= Tffa657inputTb4b4b4(Tb4b4b4)
Te6edf3destinationTff7b72.Td2a8ffannounceTb4b4b4(Tb4b4b4)
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffSent announce from Ta5d6ff"Tff7b72+Te6edf3RNSTff7b72.Td2a8ffprettyhexrepTb4b4b4(Te6edf3destinationTff7b72.Td2a8ffhashTb4b4b4)Tb4b4b4)

T8b949e# Here's a convenience function for listing all files
T8b949e# in our served directory
Tff7b72def Td2a8fflist_filesTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e# We add all entries from the directory that are
T8b949e# actual files, and does not start with "."
Tff7b72global Te6edf3serve_path
Tff7b72return Tb4b4b4[Te6edf3file Tff7b72for Te6edf3file Tff7b72in Te6edf3osTff7b72.Td2a8fflistdirTb4b4b4(Te6edf3serve_pathTb4b4b4) Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3serve_pathTb4b4b4, Te6edf3fileTb4b4b4)Tb4b4b4) Tff7b72and Te6edf3fileTb4b4b4[Tb4b4b4:T79c0ff1Tb4b4b4] Tff7b72!= Ta5d6ff"Ta5d6ff.Ta5d6ff"Tb4b4b4]

T8b949e# When a client establishes a link to our server
T8b949e# destination, this function will be called with
T8b949e# a reference to the link. We then send the client
T8b949e# a list of files hosted on the server.
Tff7b72def Td2a8ffclient_connectedTb4b4b4(Te6edf3linkTb4b4b4)Tb4b4b4:
T8b949e# Check if the served directory still exists
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisdirTb4b4b4(Te6edf3serve_pathTb4b4b4)Tb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffClient connected, sending file list...Ta5d6ff"Tb4b4b4)

Te6edf3linkTff7b72.Td2a8ffset_link_closed_callbackTb4b4b4(Te6edf3client_disconnectedTb4b4b4)

T8b949e# We pack a list of files for sending in a packet
Te6edf3data Tff7b72= Te6edf3umsgpackTff7b72.Td2a8ffpackbTb4b4b4(Te6edf3list_filesTb4b4b4(Tb4b4b4)Tb4b4b4)

T8b949e# Check the size of the packed data
Tff7b72if Tffa657lenTb4b4b4(Te6edf3dataTb4b4b4) Tff7b72<Tff7b72= Te6edf3RNSTff7b72.Td2a8ffLinkTff7b72.Td2a8ffMDUTb4b4b4:
T8b949e# If it fits in one packet, we will just
T8b949e# send it as a single packet over the link.
Te6edf3list_packet Tff7b72= Te6edf3RNSTff7b72.Td2a8ffPacketTb4b4b4(Te6edf3linkTb4b4b4, Te6edf3dataTb4b4b4)
Te6edf3list_receipt Tff7b72= Te6edf3list_packetTff7b72.Td2a8ffsendTb4b4b4(Tb4b4b4)
Te6edf3list_receiptTff7b72.Td2a8ffset_timeoutTb4b4b4(Te6edf3APP_TIMEOUTTb4b4b4)
Te6edf3list_receiptTff7b72.Td2a8ffset_delivery_callbackTb4b4b4(Te6edf3list_deliveredTb4b4b4)
Te6edf3list_receiptTff7b72.Td2a8ffset_timeout_callbackTb4b4b4(Te6edf3list_timeoutTb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffToo many files in served directory!Ta5d6ff"Tb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4)
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffYou should implement a function to split the filelist over multiple packets.Ta5d6ff"Tb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4)
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffHint: The client already supports it :)Ta5d6ff"Tb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4)
T8b949e# After this, we're just going to keep the link
T8b949e# open until the client requests a file. We'll
T8b949e# configure a function that get's called when
T8b949e# the client sends a packet with a file request.
Te6edf3linkTff7b72.Td2a8ffset_packet_callbackTb4b4b4(Te6edf3client_requestTb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffClient connected, but served path no longer exists!Ta5d6ff"Tb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4)
Te6edf3linkTff7b72.Td2a8ffteardownTb4b4b4(Tb4b4b4)

Tff7b72def Td2a8ffclient_disconnectedTb4b4b4(Te6edf3linkTb4b4b4)Tb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffClient disconnectedTa5d6ff"Tb4b4b4)

Tff7b72def Td2a8ffclient_requestTb4b4b4(Te6edf3messageTb4b4b4, Te6edf3packetTb4b4b4)Tb4b4b4:
Tff7b72global Te6edf3serve_path

Tff7b72tryTb4b4b4:
Te6edf3filename Tff7b72= Te6edf3messageTff7b72.Td2a8ffdecodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3filename Tff7b72= Tff7b72None

Tff7b72if Te6edf3filename Tff7b72in Te6edf3list_filesTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
T8b949e# If we have the requested file, we'll
T8b949e# read it and pack it as a resource
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffClient requested Tffea00\"Ta5d6ff"Tff7b72+Te6edf3filenameTff7b72+Ta5d6ff"Tffea00\"Ta5d6ff"Tb4b4b4)
Te6edf3file Tff7b72= Tffa657openTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3serve_pathTb4b4b4, Te6edf3filenameTb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffrbTa5d6ff"Tb4b4b4)
Te6edf3file_resource Tff7b72= Te6edf3RNSTff7b72.Td2a8ffResourceTb4b4b4(
Te6edf3fileTb4b4b4,
Te6edf3packetTff7b72.Td2a8fflinkTb4b4b4,
Te6edf3callbackTff7b72=Te6edf3resource_sending_concluded
Tb4b4b4)

Te6edf3file_resourceTff7b72.Td2a8fffilename Tff7b72= Te6edf3filename
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
T8b949e# If somethign went wrong, we close
T8b949e# the link
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffError while reading file Tffea00\"Ta5d6ff"Tff7b72+Te6edf3filenameTff7b72+Ta5d6ff"Tffea00\"Ta5d6ff"Tb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4)
Te6edf3packetTff7b72.Td2a8fflinkTff7b72.Td2a8ffteardownTb4b4b4(Tb4b4b4)
Tff7b72raise Te6edf3e
Tff7b72elseTb4b4b4:
T8b949e# If we don't have it, we close the link
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffClient requested an unknown fileTa5d6ff"Tb4b4b4)
Te6edf3packetTff7b72.Td2a8fflinkTff7b72.Td2a8ffteardownTb4b4b4(Tb4b4b4)

T8b949e# This function is called on the server when a
T8b949e# resource transfer concludes.
Tff7b72def Td2a8ffresource_sending_concludedTb4b4b4(Te6edf3resourceTb4b4b4)Tb4b4b4:
Tff7b72if Tffa657hasattrTb4b4b4(Te6edf3resourceTb4b4b4, Ta5d6ff"Ta5d6fffilenameTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3name Tff7b72= Te6edf3resourceTff7b72.Td2a8fffilename
Tff7b72elseTb4b4b4:
Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffresourceTa5d6ff"

Tff7b72if Te6edf3resourceTff7b72.Td2a8ffstatus Tff7b72== Te6edf3RNSTff7b72.Td2a8ffResourceTff7b72.Td2a8ffCOMPLETETb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffDone sending Tffea00\"Ta5d6ff"Tff7b72+Te6edf3nameTff7b72+Ta5d6ff"Tffea00\"Ta5d6ff to clientTa5d6ff"Tb4b4b4)
Tff7b72elif Te6edf3resourceTff7b72.Td2a8ffstatus Tff7b72== Te6edf3RNSTff7b72.Td2a8ffResourceTff7b72.Td2a8ffFAILEDTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffSending Tffea00\"Ta5d6ff"Tff7b72+Te6edf3nameTff7b72+Ta5d6ff"Tffea00\"Ta5d6ff to client failedTa5d6ff"Tb4b4b4)

Tff7b72def Td2a8fflist_deliveredTb4b4b4(Te6edf3receiptTb4b4b4)Tb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffThe file list was received by the clientTa5d6ff"Tb4b4b4)

Tff7b72def Td2a8fflist_timeoutTb4b4b4(Te6edf3receiptTb4b4b4)Tb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffSending list to client timed out, closing this linkTa5d6ff"Tb4b4b4)
Te6edf3link Tff7b72= Te6edf3receiptTff7b72.Td2a8ffdestination
Te6edf3linkTff7b72.Td2a8ffteardownTb4b4b4(Tb4b4b4)

T8b949e##########################################################
T8b949e#### Client Part #########################################
T8b949e##########################################################

T8b949e# We store a global list of files available on the server
Te6edf3server_files Tff7b72= Tb4b4b4[Tb4b4b4]

T8b949e# A reference to the server link
Te6edf3server_link Tff7b72= Tff7b72None

T8b949e# And a reference to the current download
Te6edf3current_download Tff7b72= Tff7b72None
Te6edf3current_filename Tff7b72= Tff7b72None

T8b949e# Variables to store download statistics
Te6edf3download_started Tff7b72= T79c0ff0
Te6edf3download_finished Tff7b72= T79c0ff0
Te6edf3download_time Tff7b72= T79c0ff0
Te6edf3transfer_size Tff7b72= T79c0ff0
Te6edf3file_size Tff7b72= T79c0ff0


T8b949e# This initialisation is executed when the users chooses
T8b949e# to run as a client
Tff7b72def Td2a8ffclientTb4b4b4(Te6edf3destination_hexhashTb4b4b4, Te6edf3configpathTb4b4b4)Tb4b4b4:
T8b949e# We need a binary representation of the destination
T8b949e# hash that was entered on the command line
Tff7b72tryTb4b4b4:
Te6edf3dest_len Tff7b72= Tb4b4b4(Te6edf3RNSTff7b72.Td2a8ffReticulumTff7b72.Td2a8ffTRUNCATED_HASHLENGTHTff7b72/Tff7b72/T79c0ff8Tb4b4b4)Tff7b72*T79c0ff2
Tff7b72if Tffa657lenTb4b4b4(Te6edf3destination_hexhashTb4b4b4) Tff7b72!= Te6edf3dest_lenTb4b4b4:
Tff7b72raise Tf85149ValueErrorTb4b4b4(
Ta5d6ff"Ta5d6ffDestination length is invalid, must be Tffd700{hex}Ta5d6ff hexadecimal characters (Tffd700{byte}Ta5d6ff bytes).Ta5d6ff"Tff7b72.Td2a8ffformatTb4b4b4(Tffa657hexTff7b72=Te6edf3dest_lenTb4b4b4, Te6edf3byteTff7b72=Te6edf3dest_lenTff7b72/Tff7b72/T79c0ff2Tb4b4b4)
Tb4b4b4)
Te6edf3destination_hash Tff7b72= Tffa657bytesTff7b72.Td2a8fffromhexTb4b4b4(Te6edf3destination_hexhashTb4b4b4)
Tff7b72exceptTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffInvalid destination entered. Check your input!Tffea00\nTa5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff0Tb4b4b4)

T8b949e# We must first initialise Reticulum
Te6edf3reticulum Tff7b72= Te6edf3RNSTff7b72.Td2a8ffReticulumTb4b4b4(Te6edf3configpathTb4b4b4)


T8b949e# Check if we know a path to the destination
Tff7b72if Tff7b72not Te6edf3RNSTff7b72.Td2a8ffTransportTff7b72.Td2a8ffhas_pathTb4b4b4(Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffDestination is not yet known. Requesting path and waiting for announce to arrive...Ta5d6ff"Tb4b4b4)
Te6edf3RNSTff7b72.Td2a8ffTransportTff7b72.Td2a8ffrequest_pathTb4b4b4(Te6edf3destination_hashTb4b4b4)
Tff7b72while Tff7b72not Te6edf3RNSTff7b72.Td2a8ffTransportTff7b72.Td2a8ffhas_pathTb4b4b4(Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0.1Tb4b4b4)

T8b949e# Recall the server identity
Te6edf3server_identity Tff7b72= Te6edf3RNSTff7b72.Td2a8ffIdentityTff7b72.Td2a8ffrecallTb4b4b4(Te6edf3destination_hashTb4b4b4)

T8b949e# Inform the user that we'll begin connecting
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffEstablishing link with server...Ta5d6ff"Tb4b4b4)

T8b949e# When the server identity is known, we set
T8b949e# up a destination
Te6edf3server_destination Tff7b72= Te6edf3RNSTff7b72.Td2a8ffDestinationTb4b4b4(
Te6edf3server_identityTb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffDestinationTff7b72.Td2a8ffOUTTb4b4b4,
Te6edf3RNSTff7b72.Td2a8ffDestinationTff7b72.Td2a8ffSINGLETb4b4b4,
Te6edf3APP_NAMETb4b4b4,
Ta5d6ff"Ta5d6fffiletransferTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffserverTa5d6ff"
Tb4b4b4)

T8b949e# We also want to automatically prove incoming packets
Te6edf3server_destinationTff7b72.Td2a8ffset_proof_strategyTb4b4b4(Te6edf3RNSTff7b72.Td2a8ffDestinationTff7b72.Td2a8ffPROVE_ALLTb4b4b4)

T8b949e# And create a link
Te6edf3link Tff7b72= Te6edf3RNSTff7b72.Td2a8ffLinkTb4b4b4(Te6edf3server_destinationTb4b4b4)

T8b949e# We expect any normal data packets on the link
T8b949e# to contain a list of served files, so we set
T8b949e# a callback accordingly
Te6edf3linkTff7b72.Td2a8ffset_packet_callbackTb4b4b4(Te6edf3filelist_receivedTb4b4b4)

T8b949e# We'll also set up functions to inform the
T8b949e# user when the link is established or closed
Te6edf3linkTff7b72.Td2a8ffset_link_established_callbackTb4b4b4(Te6edf3link_establishedTb4b4b4)
Te6edf3linkTff7b72.Td2a8ffset_link_closed_callbackTb4b4b4(Te6edf3link_closedTb4b4b4)

T8b949e# And set the link to automatically begin
T8b949e# downloading advertised resources
Te6edf3linkTff7b72.Td2a8ffset_resource_strategyTb4b4b4(Te6edf3RNSTff7b72.Td2a8ffLinkTff7b72.Td2a8ffACCEPT_ALLTb4b4b4)
Te6edf3linkTff7b72.Td2a8ffset_resource_started_callbackTb4b4b4(Te6edf3download_beganTb4b4b4)
Te6edf3linkTff7b72.Td2a8ffset_resource_concluded_callbackTb4b4b4(Te6edf3download_concludedTb4b4b4)

Te6edf3menuTb4b4b4(Tb4b4b4)

T8b949e# Requests the specified file from the server
Tff7b72def Td2a8ffdownloadTb4b4b4(Te6edf3filenameTb4b4b4)Tb4b4b4:
Tff7b72global Te6edf3server_linkTb4b4b4, Te6edf3menu_modeTb4b4b4, Te6edf3current_filenameTb4b4b4, Te6edf3transfer_sizeTb4b4b4, Te6edf3download_started
Te6edf3current_filename Tff7b72= Te6edf3filename
Te6edf3download_started Tff7b72= T79c0ff0
Te6edf3transfer_size Tff7b72= T79c0ff0

T8b949e# We just create a packet containing the
T8b949e# requested filename, and send it down the
T8b949e# link. We also specify we don't need a
T8b949e# packet receipt.
Te6edf3request_packet Tff7b72= Te6edf3RNSTff7b72.Td2a8ffPacketTb4b4b4(Te6edf3server_linkTb4b4b4, Te6edf3filenameTff7b72.Td2a8ffencodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3create_receiptTff7b72=Tff7b72FalseTb4b4b4)
Te6edf3request_packetTff7b72.Td2a8ffsendTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Tb4b4b4(Ta5d6ff"Ta5d6ffRequested Tffea00\"Ta5d6ff"Tff7b72+Te6edf3filenameTff7b72+Ta5d6ff"Tffea00\"Ta5d6ff from server, waiting for download to begin...Ta5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3menu_mode Tff7b72= Ta5d6ff"Ta5d6ffdownload_startedTa5d6ff"

T8b949e# This function runs a simple menu for the user
T8b949e# to select which files to download, or quit
Te6edf3menu_mode Tff7b72= Tff7b72None
Tff7b72def Td2a8ffmenuTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72global Te6edf3server_filesTb4b4b4, Te6edf3server_link
T8b949e# Wait until we have a filelist
Tff7b72while Tffa657lenTb4b4b4(Te6edf3server_filesTb4b4b4) Tff7b72== T79c0ff0Tb4b4b4:
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0.1Tb4b4b4)
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffReady!Ta5d6ff"Tb4b4b4)
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0.5Tb4b4b4)

Tff7b72global Te6edf3menu_mode
Te6edf3menu_mode Tff7b72= Ta5d6ff"Ta5d6ffmainTa5d6ff"
Te6edf3should_quit Tff7b72= Tff7b72False
Tff7b72while Tb4b4b4(Tff7b72not Te6edf3should_quitTb4b4b4)Tb4b4b4:
Te6edf3print_menuTb4b4b4(Tb4b4b4)

Tff7b72while Tff7b72not Te6edf3menu_mode Tff7b72== Ta5d6ff"Ta5d6ffmainTa5d6ff"Tb4b4b4:
T8b949e# Wait
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0.25Tb4b4b4)

Te6edf3user_input Tff7b72= Tffa657inputTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3user_input Tff7b72== Ta5d6ff"Ta5d6ffqTa5d6ff" Tff7b72or Te6edf3user_input Tff7b72== Ta5d6ff"Ta5d6ffquitTa5d6ff" Tff7b72or Te6edf3user_input Tff7b72== Ta5d6ff"Ta5d6ffexitTa5d6ff"Tb4b4b4:
Te6edf3should_quit Tff7b72= Tff7b72True
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Tff7b72if Te6edf3user_input Tff7b72in Te6edf3server_filesTb4b4b4:
Te6edf3downloadTb4b4b4(Te6edf3user_inputTb4b4b4)
Tff7b72elseTb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72if T79c0ff0 Tff7b72<Tff7b72= Tffa657intTb4b4b4(Te6edf3user_inputTb4b4b4) Tff7b72< Tffa657lenTb4b4b4(Te6edf3server_filesTb4b4b4)Tb4b4b4:
Te6edf3downloadTb4b4b4(Te6edf3server_filesTb4b4b4[Tffa657intTb4b4b4(Te6edf3user_inputTb4b4b4)Tb4b4b4]Tb4b4b4)
Tff7b72exceptTb4b4b4:
Tff7b72pass

Tff7b72if Te6edf3should_quitTb4b4b4:
Te6edf3server_linkTff7b72.Td2a8ffteardownTb4b4b4(Tb4b4b4)

T8b949e# Prints out menus or screens for the
T8b949e# various states of the client program.
T8b949e# It's simple and quite uninteresting.
T8b949e# I won't go into detail here. Just
T8b949e# strings basically.
Tff7b72def Td2a8ffprint_menuTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72global Te6edf3menu_modeTb4b4b4, Te6edf3download_timeTb4b4b4, Te6edf3download_startedTb4b4b4, Te6edf3download_finishedTb4b4b4, Te6edf3transfer_sizeTb4b4b4, Te6edf3file_size

Tff7b72if Te6edf3menu_mode Tff7b72== Ta5d6ff"Ta5d6ffmainTa5d6ff"Tb4b4b4:
Te6edf3clear_screenTb4b4b4(Tb4b4b4)
Te6edf3print_filelistTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffSelect a file to download by entering name or number, or q to quitTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Tb4b4b4(Ta5d6ff"Ta5d6ff> Ta5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3endTff7b72=Ta5d6ff'Ta5d6ff Ta5d6ff'Tb4b4b4)
Tff7b72elif Te6edf3menu_mode Tff7b72== Ta5d6ff"Ta5d6ffdownload_startedTa5d6ff"Tb4b4b4:
Te6edf3download_began Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
Tff7b72while Te6edf3menu_mode Tff7b72== Ta5d6ff"Ta5d6ffdownload_startedTa5d6ff"Tb4b4b4:
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0.1Tb4b4b4)
Tff7b72if Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72> Te6edf3download_beganTff7b72+Te6edf3APP_TIMEOUTTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffThe download timed outTa5d6ff"Tb4b4b4)
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff1Tb4b4b4)
Te6edf3server_linkTff7b72.Td2a8ffteardownTb4b4b4(Tb4b4b4)

Tff7b72if Te6edf3menu_mode Tff7b72== Ta5d6ff"Ta5d6ffdownloadingTa5d6ff"Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffDownload startedTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tff7b72while Te6edf3menu_mode Tff7b72== Ta5d6ff"Ta5d6ffdownloadingTa5d6ff"Tb4b4b4:
Tff7b72global Te6edf3current_download
Te6edf3percent Tff7b72= Tffa657roundTb4b4b4(Te6edf3current_downloadTff7b72.Td2a8ffget_progressTb4b4b4(Tb4b4b4) Tff7b72* T79c0ff100.0Tb4b4b4, T79c0ff1Tb4b4b4)
Tffa657printTb4b4b4(Tb4b4b4(Ta5d6ff"Tffea00\rTa5d6ffProgress: Ta5d6ff"Tff7b72+Tffa657strTb4b4b4(Te6edf3percentTb4b4b4)Tff7b72+Ta5d6ff"Ta5d6ff Ta5d6ff%Ta5d6ff Ta5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3endTff7b72=Ta5d6ff'Ta5d6ff Ta5d6ff'Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffstdoutTff7b72.Td2a8ffflushTb4b4b4(Tb4b4b4)
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0.1Tb4b4b4)

Tff7b72if Te6edf3menu_mode Tff7b72== Ta5d6ff"Ta5d6ffsave_errorTa5d6ff"Tb4b4b4:
Tffa657printTb4b4b4(Tb4b4b4(Ta5d6ff"Tffea00\rTa5d6ffProgress: 100.0 Ta5d6ff%Ta5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3endTff7b72=Ta5d6ff'Ta5d6ff Ta5d6ff'Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffstdoutTff7b72.Td2a8ffflushTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffCould not write downloaded file to diskTa5d6ff"Tb4b4b4)
Te6edf3current_downloadTff7b72.Td2a8ffstatus Tff7b72= Te6edf3RNSTff7b72.Td2a8ffResourceTff7b72.Td2a8ffFAILED
Te6edf3menu_mode Tff7b72= Ta5d6ff"Ta5d6ffdownload_concludedTa5d6ff"

Tff7b72if Te6edf3menu_mode Tff7b72== Ta5d6ff"Ta5d6ffdownload_concludedTa5d6ff"Tb4b4b4:
Tff7b72if Te6edf3current_downloadTff7b72.Td2a8ffstatus Tff7b72== Te6edf3RNSTff7b72.Td2a8ffResourceTff7b72.Td2a8ffCOMPLETETb4b4b4:
Tffa657printTb4b4b4(Tb4b4b4(Ta5d6ff"Tffea00\rTa5d6ffProgress: 100.0 Ta5d6ff%Ta5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3endTff7b72=Ta5d6ff'Ta5d6ff Ta5d6ff'Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffstdoutTff7b72.Td2a8ffflushTb4b4b4(Tb4b4b4)

T8b949e# Print statistics
Te6edf3hoursTb4b4b4, Te6edf3rem Tff7b72= Tffa657divmodTb4b4b4(Te6edf3download_timeTb4b4b4, T79c0ff3600Tb4b4b4)
Te6edf3minutesTb4b4b4, Te6edf3seconds Tff7b72= Tffa657divmodTb4b4b4(Te6edf3remTb4b4b4, T79c0ff60Tb4b4b4)
Te6edf3timestring Tff7b72= Ta5d6ff"Tffd700{:0>2}Ta5d6ff:Tffd700{:0>2}Ta5d6ff:Tffd700{:05.2f}Ta5d6ff"Tff7b72.Td2a8ffformatTb4b4b4(Tffa657intTb4b4b4(Te6edf3hoursTb4b4b4)Tb4b4b4,Tffa657intTb4b4b4(Te6edf3minutesTb4b4b4)Tb4b4b4,Te6edf3secondsTb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff--- Statistics -----Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\tTa5d6ffTime taken : Ta5d6ff"Tff7b72+Te6edf3timestringTb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\tTa5d6ffFile size : Ta5d6ff"Tff7b72+Te6edf3size_strTb4b4b4(Te6edf3file_sizeTb4b4b4)Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\tTa5d6ffData transferred : Ta5d6ff"Tff7b72+Te6edf3size_strTb4b4b4(Te6edf3transfer_sizeTb4b4b4)Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\tTa5d6ffEffective rate : Ta5d6ff"Tff7b72+Te6edf3size_strTb4b4b4(Te6edf3file_sizeTff7b72/Te6edf3download_timeTb4b4b4, Te6edf3suffixTff7b72=Ta5d6ff'Ta5d6ffbTa5d6ff'Tb4b4b4)Tff7b72+Ta5d6ff"Ta5d6ff/sTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\tTa5d6ffTransfer rate : Ta5d6ff"Tff7b72+Te6edf3size_strTb4b4b4(Te6edf3transfer_sizeTff7b72/Te6edf3download_timeTb4b4b4, Te6edf3suffixTff7b72=Ta5d6ff'Ta5d6ffbTa5d6ff'Tb4b4b4)Tff7b72+Ta5d6ff"Ta5d6ff/sTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffThe download completed! Press enter to return to the menu.Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tffa657inputTb4b4b4(Tb4b4b4)

Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffThe download failed! Press enter to return to the menu.Ta5d6ff"Tb4b4b4)
Tffa657inputTb4b4b4(Tb4b4b4)

Te6edf3current_download Tff7b72= Tff7b72None
Te6edf3menu_mode Tff7b72= Ta5d6ff"Ta5d6ffmainTa5d6ff"
Te6edf3print_menuTb4b4b4(Tb4b4b4)

T8b949e# This function prints out a list of files
T8b949e# on the connected server.
Tff7b72def Td2a8ffprint_filelistTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72global Te6edf3server_files
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffFiles on server:Ta5d6ff"Tb4b4b4)
Tff7b72for Te6edf3indexTb4b4b4,Te6edf3file Tff7b72in Tffa657enumerateTb4b4b4(Te6edf3server_filesTb4b4b4)Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\tTa5d6ff(Ta5d6ff"Tff7b72+Tffa657strTb4b4b4(Te6edf3indexTb4b4b4)Tff7b72+Ta5d6ff"Ta5d6ff)Tffea00\tTa5d6ff"Tff7b72+Te6edf3fileTb4b4b4)

Tff7b72def Td2a8fffilelist_receivedTb4b4b4(Te6edf3filelist_dataTb4b4b4, Te6edf3packetTb4b4b4)Tb4b4b4:
Tff7b72global Te6edf3server_filesTb4b4b4, Te6edf3menu_mode
Tff7b72tryTb4b4b4:
T8b949e# Unpack the list and extend our
T8b949e# local list of available files
Te6edf3filelist Tff7b72= Te6edf3umsgpackTff7b72.Td2a8ffunpackbTb4b4b4(Te6edf3filelist_dataTb4b4b4)
Tff7b72for Te6edf3file Tff7b72in Te6edf3filelistTb4b4b4:
Tff7b72if Tff7b72not Te6edf3file Tff7b72in Te6edf3server_filesTb4b4b4:
Te6edf3server_filesTff7b72.Td2a8ffappendTb4b4b4(Te6edf3fileTb4b4b4)

T8b949e# If the menu is already visible,
T8b949e# we'll update it with what was
T8b949e# just received
Tff7b72if Te6edf3menu_mode Tff7b72== Ta5d6ff"Ta5d6ffmainTa5d6ff"Tb4b4b4:
Te6edf3print_menuTb4b4b4(Tb4b4b4)
Tff7b72exceptTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffInvalid file list data received, closing linkTa5d6ff"Tb4b4b4)
Te6edf3packetTff7b72.Td2a8fflinkTff7b72.Td2a8ffteardownTb4b4b4(Tb4b4b4)

T8b949e# This function is called when a link
T8b949e# has been established with the server
Tff7b72def Td2a8fflink_establishedTb4b4b4(Te6edf3linkTb4b4b4)Tb4b4b4:
T8b949e# We store a reference to the link
T8b949e# instance for later use
Tff7b72global Te6edf3server_link
Te6edf3server_link Tff7b72= Te6edf3link

T8b949e# Inform the user that the server is
T8b949e# connected
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffLink established with serverTa5d6ff"Tb4b4b4)
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffWaiting for filelist...Ta5d6ff"Tb4b4b4)

T8b949e# And set up a small job to check for
T8b949e# a potential timeout in receiving the
T8b949e# file list
Te6edf3thread Tff7b72= Te6edf3threadingTff7b72.Td2a8ffThreadTb4b4b4(Te6edf3targetTff7b72=Te6edf3filelist_timeout_jobTb4b4b4, Te6edf3daemonTff7b72=Tff7b72TrueTb4b4b4)
Te6edf3threadTff7b72.Td2a8ffstartTb4b4b4(Tb4b4b4)

T8b949e# This job just sleeps for the specified
T8b949e# time, and then checks if the file list
T8b949e# was received. If not, the program will
T8b949e# exit.
Tff7b72def Td2a8fffilelist_timeout_jobTb4b4b4(Tb4b4b4)Tb4b4b4:
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(Te6edf3APP_TIMEOUTTb4b4b4)

Tff7b72global Te6edf3server_files
Tff7b72if Tffa657lenTb4b4b4(Te6edf3server_filesTb4b4b4) Tff7b72== T79c0ff0Tb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffTimed out waiting for filelist, exitingTa5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff0Tb4b4b4)


T8b949e# When a link is closed, we'll inform the
T8b949e# user, and exit the program
Tff7b72def Td2a8fflink_closedTb4b4b4(Te6edf3linkTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3linkTff7b72.Td2a8ffteardown_reason Tff7b72== Te6edf3RNSTff7b72.Td2a8ffLinkTff7b72.Td2a8ffTIMEOUTTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffThe link timed out, exiting nowTa5d6ff"Tb4b4b4)
Tff7b72elif Te6edf3linkTff7b72.Td2a8ffteardown_reason Tff7b72== Te6edf3RNSTff7b72.Td2a8ffLinkTff7b72.Td2a8ffDESTINATION_CLOSEDTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffThe link was closed by the server, exiting nowTa5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffLink closed, exiting nowTa5d6ff"Tb4b4b4)
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff1.5Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff0Tb4b4b4)

T8b949e# When RNS detects that the download has
T8b949e# started, we'll update our menu state
T8b949e# so the user can be shown a progress of
T8b949e# the download.
Tff7b72def Td2a8ffdownload_beganTb4b4b4(Te6edf3resourceTb4b4b4)Tb4b4b4:
Tff7b72global Te6edf3menu_modeTb4b4b4, Te6edf3current_downloadTb4b4b4, Te6edf3download_startedTb4b4b4, Te6edf3transfer_sizeTb4b4b4, Te6edf3file_size
Te6edf3current_download Tff7b72= Te6edf3resource
Tff7b72if Te6edf3download_started Tff7b72== T79c0ff0Tb4b4b4:
Te6edf3download_started Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
Te6edf3transfer_size Tff7b72+Tff7b72= Te6edf3resourceTff7b72.Td2a8ffsize
Te6edf3file_size Tff7b72= Te6edf3resourceTff7b72.Td2a8fftotal_size
Te6edf3menu_mode Tff7b72= Ta5d6ff"Ta5d6ffdownloadingTa5d6ff"

T8b949e# When the download concludes, successfully
T8b949e# or not, we'll update our menu state and
T8b949e# inform the user about how it all went.
Tff7b72def Td2a8ffdownload_concludedTb4b4b4(Te6edf3resourceTb4b4b4)Tb4b4b4:
Tff7b72global Te6edf3menu_modeTb4b4b4, Te6edf3current_filenameTb4b4b4, Te6edf3download_startedTb4b4b4, Te6edf3download_finishedTb4b4b4, Te6edf3download_time
Te6edf3download_finished Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4)
Te6edf3download_time Tff7b72= Te6edf3download_finished Tff7b72- Te6edf3download_started

Te6edf3saved_filename Tff7b72= Te6edf3current_filename

Tff7b72if Te6edf3resourceTff7b72.Td2a8ffstatus Tff7b72== Te6edf3RNSTff7b72.Td2a8ffResourceTff7b72.Td2a8ffCOMPLETETb4b4b4:
Te6edf3counter Tff7b72= T79c0ff0
Tff7b72while Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3saved_filenameTb4b4b4)Tb4b4b4:
Te6edf3counter Tff7b72+Tff7b72= T79c0ff1
Te6edf3saved_filename Tff7b72= Te6edf3current_filenameTff7b72+Ta5d6ff"Ta5d6ff.Ta5d6ff"Tff7b72+Tffa657strTb4b4b4(Te6edf3counterTb4b4b4)

Tff7b72tryTb4b4b4:
Te6edf3file Tff7b72= Tffa657openTb4b4b4(Te6edf3saved_filenameTb4b4b4, Ta5d6ff"Ta5d6ffwbTa5d6ff"Tb4b4b4)
Te6edf3fileTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3resourceTff7b72.Td2a8ffdataTff7b72.Td2a8ffreadTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3fileTff7b72.Td2a8ffcloseTb4b4b4(Tb4b4b4)
Te6edf3menu_mode Tff7b72= Ta5d6ff"Ta5d6ffdownload_concludedTa5d6ff"
Tff7b72exceptTb4b4b4:
Te6edf3menu_mode Tff7b72= Ta5d6ff"Ta5d6ffsave_errorTa5d6ff"
Tff7b72elseTb4b4b4:
Te6edf3menu_mode Tff7b72= Ta5d6ff"Ta5d6ffdownload_concludedTa5d6ff"

T8b949e# A convenience function for printing a human-
T8b949e# readable file size
Tff7b72def Td2a8ffsize_strTb4b4b4(Te6edf3numTb4b4b4, Te6edf3suffixTff7b72=Ta5d6ff'Ta5d6ffBTa5d6ff'Tb4b4b4)Tb4b4b4:
Te6edf3units Tff7b72= Tb4b4b4[Ta5d6ff'Ta5d6ff'Tb4b4b4,Ta5d6ff'Ta5d6ffKiTa5d6ff'Tb4b4b4,Ta5d6ff'Ta5d6ffMiTa5d6ff'Tb4b4b4,Ta5d6ff'Ta5d6ffGiTa5d6ff'Tb4b4b4,Ta5d6ff'Ta5d6ffTiTa5d6ff'Tb4b4b4,Ta5d6ff'Ta5d6ffPiTa5d6ff'Tb4b4b4,Ta5d6ff'Ta5d6ffEiTa5d6ff'Tb4b4b4,Ta5d6ff'Ta5d6ffZiTa5d6ff'Tb4b4b4]
Te6edf3last_unit Tff7b72= Ta5d6ff'Ta5d6ffYiTa5d6ff'

Tff7b72if Te6edf3suffix Tff7b72== Ta5d6ff'Ta5d6ffbTa5d6ff'Tb4b4b4:
Te6edf3num Tff7b72*Tff7b72= T79c0ff8
Te6edf3units Tff7b72= Tb4b4b4[Ta5d6ff'Ta5d6ff'Tb4b4b4,Ta5d6ff'Ta5d6ffKTa5d6ff'Tb4b4b4,Ta5d6ff'Ta5d6ffMTa5d6ff'Tb4b4b4,Ta5d6ff'Ta5d6ffGTa5d6ff'Tb4b4b4,Ta5d6ff'Ta5d6ffTTa5d6ff'Tb4b4b4,Ta5d6ff'Ta5d6ffPTa5d6ff'Tb4b4b4,Ta5d6ff'Ta5d6ffETa5d6ff'Tb4b4b4,Ta5d6ff'Ta5d6ffZTa5d6ff'Tb4b4b4]
Te6edf3last_unit Tff7b72= Ta5d6ff'Ta5d6ffYTa5d6ff'

Tff7b72for Te6edf3unit Tff7b72in Te6edf3unitsTb4b4b4:
Tff7b72if Tffa657absTb4b4b4(Te6edf3numTb4b4b4) Tff7b72< T79c0ff1024.0Tb4b4b4:
Tff7b72return Ta5d6ff"Tffd700%3.2fTa5d6ff Tffd700%sTffd700%sTa5d6ff" Tff7b72% Tb4b4b4(Te6edf3numTb4b4b4, Te6edf3unitTb4b4b4, Te6edf3suffixTb4b4b4)
Te6edf3num Tff7b72/Tff7b72= T79c0ff1024.0
Tff7b72return Ta5d6ff"Tffd700%.2fTa5d6ff Tffd700%sTffd700%sTa5d6ff" Tff7b72% Tb4b4b4(Te6edf3numTb4b4b4, Te6edf3last_unitTb4b4b4, Te6edf3suffixTb4b4b4)

T8b949e# A convenience function for clearing the screen
Tff7b72def Td2a8ffclear_screenTb4b4b4(Tb4b4b4)Tb4b4b4:
Te6edf3osTff7b72.Td2a8ffsystemTb4b4b4(Ta5d6ff'Ta5d6ffclsTa5d6ff' Tff7b72if Te6edf3osTff7b72.Td2a8ffnameTff7b72==Ta5d6ff'Ta5d6ffntTa5d6ff' Tff7b72else Ta5d6ff'Ta5d6ffclearTa5d6ff'Tb4b4b4)

T8b949e##########################################################
T8b949e#### Program Startup #####################################
T8b949e##########################################################

T8b949e# This part of the program runs at startup,
T8b949e# and parses input of from the user, and then
T8b949e# starts up the desired program mode.
Tff7b72if Tff7b72__name__ Tff7b72== Ta5d6ff"Ta5d6ff__main__Ta5d6ff"Tb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3parser Tff7b72= Te6edf3argparseTff7b72.Td2a8ffArgumentParserTb4b4b4(
Te6edf3descriptionTff7b72=Ta5d6ff"Ta5d6ffSimple file transfer server and client utilityTa5d6ff"
Tb4b4b4)

Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(
Ta5d6ff"Ta5d6ff-sTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ff--serveTa5d6ff"Tb4b4b4,
Te6edf3actionTff7b72=Ta5d6ff"Ta5d6ffstoreTa5d6ff"Tb4b4b4,
Te6edf3metavarTff7b72=Ta5d6ff"Ta5d6ffdirTa5d6ff"Tb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffserve a directory of files to clientsTa5d6ff"
Tb4b4b4)

Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(
Ta5d6ff"Ta5d6ff--configTa5d6ff"Tb4b4b4,
Te6edf3actionTff7b72=Ta5d6ff"Ta5d6ffstoreTa5d6ff"Tb4b4b4,
Te6edf3defaultTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffpath to alternative Reticulum config directoryTa5d6ff"Tb4b4b4,
Tffa657typeTff7b72=Tffa657str
Tb4b4b4)

Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(
Ta5d6ff"Ta5d6ffdestinationTa5d6ff"Tb4b4b4,
Te6edf3nargsTff7b72=Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4,
Te6edf3defaultTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffhexadecimal hash of the server destinationTa5d6ff"Tb4b4b4,
Tffa657typeTff7b72=Tffa657str
Tb4b4b4)

Te6edf3args Tff7b72= Te6edf3parserTff7b72.Td2a8ffparse_argsTb4b4b4(Tb4b4b4)

Tff7b72if Te6edf3argsTff7b72.Td2a8ffconfigTb4b4b4:
Te6edf3configarg Tff7b72= Te6edf3argsTff7b72.Td2a8ffconfig
Tff7b72elseTb4b4b4:
Te6edf3configarg Tff7b72= Tff7b72None

Tff7b72if Te6edf3argsTff7b72.Td2a8ffserveTb4b4b4:
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisdirTb4b4b4(Te6edf3argsTff7b72.Td2a8ffserveTb4b4b4)Tb4b4b4:
Te6edf3serverTb4b4b4(Te6edf3configargTb4b4b4, Te6edf3argsTff7b72.Td2a8ffserveTb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6ff"Ta5d6ffThe specified directory does not existTa5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Tff7b72if Tb4b4b4(Te6edf3argsTff7b72.Td2a8ffdestination Tff7b72== Tff7b72NoneTb4b4b4)Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffprint_helpTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3clientTb4b4b4(Te6edf3argsTff7b72.Td2a8ffdestinationTb4b4b4, Te6edf3configargTb4b4b4)

Tff7b72except Tf85149KeyboardInterruptTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff0Tb4b4b4)


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────